/**
 * 
 */
package Verifier.ArithmeticSystem;

import java.math.BigInteger;

/**
 * Represents an element inside a modular group.
 * 
 * @author Moshe Baavur
 *
 */
public class ModularGroupElement extends GroupElement {
	// the field which contains the element
	ModularGroup containerGroup;
	// representation number of the element in the field
	BigInteger element;
	
	/**
	 * Creates a new group element. The group should contain this element, which means the element should be a number
	 * between 0 and the (container group's order - 1) and should answer the criteria of belonging to the group.
	 * 
	 * @param element the number representing of the element in the group.
	 * @param containerGroup the group which contains the element.
	 * @throws Exception if the element does not belong to the group.
	 */
	public ModularGroupElement(BigInteger element, ModularGroup containerGroup) throws Exception {
		this.element = element;
		
		// need to check for belonging to the field
		if (containerGroup.doesElementBelong(this)) {
			this.containerGroup = containerGroup;
		} else {
			throw new Exception("Modular Group Element: The element " + element.toString() +" does not belong to the group with order " + containerGroup.getContainingGroupOrder().toString());
		}
	}
	
	/**
	 * Returns the element's representation number in the group.
	 * This number is between 0 and the (group's order - 1).
	 * 
	 * @return the element representation number in the group. 
	 */
	public BigInteger getElement() {
		return (element);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#multiply(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public GroupElement multiply(GroupElement other) throws Exception {
		// if the other is a product element, should go to the product element's multiply method which handles calculations better
		if (other instanceof ProductGroupElement) {
			return (other.divide(this));
		}
		ModularGroupElement otherElement = (ModularGroupElement)other;
		
		// if the fields are not the same, we should throw an exception
		if (!containerGroup.equals(otherElement.containerGroup)) {
			throw new Exception("Modular Group Element: Multiply: can't multiply elements from different groups");
		}
		
		// multiply the big integers
		BigInteger result = element.multiply(otherElement.element);
		// now keep the result inside the group using modulus
		result = result.mod(containerGroup.getContainingGroupOrder());
		
		// create the new group element with the field containing this and the result of the calculations
		ModularGroupElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new ModularGroupElement(result, containerGroup);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#power(java.math.BigInteger)
	 */
	@Override
	public GroupElement power(BigInteger exponent) {
		// calculate this^exponent with the "modding" included
		BigInteger result = element.modPow(exponent, containerGroup.getContainingGroupOrder());
		
		// create the new group element with the group containing this and the result of the calculations
		ModularGroupElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new ModularGroupElement(result, containerGroup);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#divide(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public GroupElement divide(GroupElement other) throws Exception {
		// if the other is a product element, should go to the product element's divide method which handles calculations better
		if (other instanceof ProductGroupElement) {
			return (other.divide(this));
		}
		ModularGroupElement otherElement = (ModularGroupElement)other;
		
		// if the fields are not the same, we should throw an exception
		if (!containerGroup.equals(otherElement.containerGroup)) {
			throw new Exception("Modular Group Element: Divide: can't divide elements from different groups");
		}
		
		// multiply the big integers
		BigInteger result = element.multiply(otherElement.element.modInverse(containerGroup.getContainingGroupOrder()));
		// now keep the result inside the group using modulus
		result = result.mod(containerGroup.getContainingGroupOrder());
		
		// create the new group element with the field containing this and the result of the calculations
		ModularGroupElement newElement = null;
		try {
			// this line should always succeed due to the modular calculation we did before, so the try-catch block is for java's safety check
			newElement = new ModularGroupElement(result, containerGroup);
		} catch (Exception e) {}
		
		return (newElement);
	}
	
	/**
	 * Compare this to obj. They are equal only if obj is of type ModularGroupElement and their group and element are equal.
	 * 
	 * @param obj the object to compare to this.
	 * @return true if and only if obj is of type ModularGroupElement and their group and the element are equal to this.
	 */
	@Override
	public boolean equals(Object obj) {
		// if the object is a ModularGroupElement, then compare them using their element and fields and not the address
		if (obj instanceof ModularGroupElement) {
			ModularGroupElement objElement = (ModularGroupElement)obj;
			
			return (containerGroup.equals(objElement.containerGroup) && element.equals(objElement.element));
		}
		// otherwise, they are not equal
		return (false);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#getGroup()
	 */
	@Override
	public Group getGroup() {
		return (containerGroup);
	}
}